#include "Port.h"

//************************************
// Method:    	Port_Open						打开串口函数
// Programmer:  lww
// Time:		2019/2/16
// Returns:   	int								成功：返回串口设备描述符；失败：返回-1
// Parameter: 	char * Port_Name				串口设备名称
// Parameter: 	bool Is_Block					是否阻塞（true：阻塞；false：非阻塞）
// Parameter: 	int Port_Mode					串口工作模式
// Parameter: 	unsigned int Band_Rate			波特率
// Parameter: 	unsigned int Data_Bits			数据位
// Parameter: 	char Check_Bit					校验位
// Parameter: 	char Stop_Bit					停止位
//************************************
int Port_Open(const char* Port_Name, bool Is_Block, int Port_Mode, unsigned int Band_Rate, unsigned int Data_Bits, char Check_Bit, char Stop_Bit)
{
#define WRONG_DEAL	{\
	printf("Port deal failed in %s!\r\n", Port_Name);\
	close(fd);\
	return -1;\
}

	int fd;			//< 定义串口设备描述符
	struct termios Term_New,Term_Old;


	//< 打开串口
	if ((fd = open(Port_Name, Port_Mode)) < 0)
	{
		perror("Open port failed!\n");
		WRONG_DEAL;
	}

	//< 保存原有串口配置		duan0530
	if (tcgetattr(fd, &Term_Old) != 0)
	{
		perror("Save Port_Old failed!\n");
		WRONG_DEAL;
	}

	//< 清空配置结构体
	bzero(&Term_New, sizeof(Term_New));

	////< 读取原有串口配置，在原有串口配置的基础上进行相关属性的设置和更改
	//if (tcgetattr(fd, &Term_New) != 0)
	//{
	//	perror("Same old port termios failed!\n");
	//	WRONG_DEAL;
	//}

	/* 有两个可以进行控制串口阻塞性（同时控制read和write）：
			一个是在打开串口的时候，open函数是否带O_NDELAY；
			第二个是可以在打开串口之后通过fcntl()函数进行控制。
	*/
	if (Is_Block == true)
	{
		if (fcntl(fd, F_SETFL, 0) < 0)			//< 设置串口阻塞
		{
			perror("Set port bolck failed!\n");
			WRONG_DEAL;
		}
	}
	else
	{
		if (fcntl(fd, F_SETFL, FNDELAY) < 0)	//< 设置串口非阻塞
		{
			perror("Set port unbolck failed!\n");
			WRONG_DEAL;
		}
	}

	//< 配置控制选项中满足：
	//<		CREAD——开启串行数据接收；
	//<		CLOCAL——打开本地连接模式（此选项可忽略串口信号线的状态）
	Term_New.c_cflag |= (CLOCAL | CREAD);

	//< 设置数据位
	Term_New.c_cflag &= ~CSIZE;
	Term_New.c_cflag |= Data_Bits;

	//< 设置奇偶校验位
	switch (Check_Bit)
	{
	case 1:		//< 奇校验
		Term_New.c_cflag |= PARENB;				//< 开启奇偶校验
		Term_New.c_iflag |= (INPCK | ISTRIP);	//< INPCK打开输入奇偶校验；ISTRIP去除字符的第八个比特
		//< Term_New.c_iflag |= INPCK;			//< duan0530 修改
		Term_New.c_cflag |= PARODD;				//< 启用奇校验(默认为偶校验)
		break;
	case 2:		//< 偶校验
		Term_New.c_cflag |= PARENB;				//< 开启奇偶校验
		Term_New.c_iflag |= (INPCK | ISTRIP);	//< 打开输入奇偶校验并去除字符第八个比特
		//< Term_New.c_iflag |= INPCK;			//< duan0602 修改
		Term_New.c_cflag &= ~PARODD;			//< 启用偶校验
		break;
	default:	//< 无校验
		Term_New.c_cflag &= ~PARENB;
		break;
	}

	//< duan0602禁止将所有接受收的字符裁剪为7比特
	Term_New.c_iflag &= ~ISTRIP;
 


	//< 设置波特率
	cfsetispeed(&Term_New, Band_Rate);
	cfsetospeed(&Term_New, Band_Rate);

	//< 设置停止位，若停止位为1，则清除CSTOPB，若停止位为2，则激活CSTOPB
	if (Stop_Bit == 2)
	{
		Term_New.c_cflag |= CSTOPB;		//< CSTOPB表示送两位停止位
	}
	else	//< 设置停止位为1位
	{
		Term_New.c_cflag &= ~CSTOPB;	//< 默认为一位停止位
	}

	/* 
		VTIME——定义要求等待的零到几百毫秒的值(通常是一个8位的unsigned char变量)。
		VMIN——定义了要求等待的最小字节数, 这个字节数可能是0。
		只有设置为阻塞时这两个参数才有效，仅针对于读操作。
		
		对于read(fd,&buf,8)函数，该设置具有如下四种用法：

			1.
			options.c_cc[VTIME] = 0;
			options.c_cc[VMIN] = 0;
			VMIN = 0，当缓冲区字节数 >= 0 时进行读操作，实际上这时读串口操作并未被阻塞，因为条件始终被满足。

			2.
			options.c_cc[VTIME] = 0;
			options.c_cc[VMIN] = 1;
			VMIN = 1，当缓冲区字节数 >= 1 时进行读操作，当没有数据时读串口操作被阻塞。

			3.
			options.c_cc[VTIME] = 0;
			options.c_cc[VMIN] = 4;
			VMIN = 4，当缓冲区字节数 >= 4 时进行读操作，否则读串口操作被阻塞。每次读出的最大字节数由read函数中第三个参数决定。直到缓冲区剩下的数据< read 第三个参数 并且< 4 （如果这时read第三参数为 1 则进行4次读操作直至读完缓冲区，如read第三参数为2，连续进行读操作，直至缓冲区空或还剩一个字符）。没有设置VTIME，剩下的字符没有确定的期限，直到下次满足读条件的时候才被读出。

			4.
			options.c_cc[VTIME] = 10; //单位百毫秒
			options.c_cc[VMIN] = 4;
			同3.3的区别就是，没满足条件或读缓冲区中剩下的数据会在1秒（10百毫秒）后读出。另外特别注意的是当设置VTIME后，如果read第三个参数小于VMIN ，将会将VMIN 修改为read的第三个参数，即使用read(fd,&buf,2);，以上设置变为：
			options.c_cc[VTIME] = 10;
			options.c_cc[VMIN] = 2;
	*/
	Term_New.c_cc[VTIME] = 0;	//< 读取时的超时时间
	Term_New.c_cc[VMIN] = 0;	//< 读取时的最小字符数

	Term_New.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);	//< 串口工作在原始模式，在原始模式下，串口输入数据是不经过处理的，在串口接口接收的数据被完整保留

	//< tcflush清空终端未完成的输入/输出请求及数据；TCIFLUSH表示清空正收到的数据，且不读取出来
	tcflush(fd, TCIFLUSH);

	//< 不进行该项设置，串口会将\n作为\r\n发送
	Term_New.c_oflag &= ~OPOST;

	//< 激活配置使其生效
	if ((tcsetattr(fd, TCSANOW, &Term_New)) != 0)
	{
		perror("Port active failed!\n");
		WRONG_DEAL;
	}

#undef WRONG_DEAL

	return fd;
}


//************************************
// Method:    	Port_Recv					串口接收函数
// Programmer:  lww
// Time:		2019/2/16
// Returns:   	int							串口接收数据字节数
// Parameter: 	int fd						串口设备描述符
// Parameter: 	char * Data					存放串口接收到的数据的内存区域的指针
// Parameter: 	int len						接收的数据长度
// Parameter: 	unsigned int wait_ms		超时设置，如设置为0，则一直阻塞等待，非阻塞状态下该参数无效
// Parameter: 	bool Is_Block				是否以阻塞方式读取数据，true：是，false：否
//************************************
int Port_Recv(int fd, void* Data, int len,unsigned int wait_ms, bool Is_Block)
{
	int _iLen = 0;

	if (Is_Block)
		_iLen = Port_Read_Block(fd, Data, len, wait_ms);
	else
		_iLen = Port_Read_UnBlock(fd, Data, len);

	return _iLen;
}


/*从串口读数据*/
/*
	ssize_t read(int fd, void *buf, size_t count)
	read系统调用中可能遇到的情况及其处理方法：
		1.	调用返回值等于count，读取的数据存放在buf指向的内存中，结果与预期一致；
		2.	调用返回一个大于0小于count的值，读取的字节数存放在buf指向的内存中。出现这种情况可能是一个信号打断了读取过程，或在读取过程中发生了一个错误，或读取
			的有效字节数大于0但不足count个，或在读入count个字节前文件已经结束。如果读取的过程被信号打断则可以再次进行读取。
		3.	调用返回0，说明文件已经结束，没有可以读入的数据。
		4.	调用阻塞，说明没有可读的数据，这种情况下如果以非阻塞方式操作文件，那么会立即返回错误。
		5.	调用返回-1，并且errno变量被设置为EINTR，表示在读入有效字节前收到一个信号，这种情况可以重新进行读操作。
		6.	调用返回-1，并且errno变量被设置为EAGAIN，这说明是在非阻塞方式下读文件，并且没有可读的数据。
		7.	调用返回-1，并且errno变量被设置为非EINTR或EAGAIN的值，表示有其他类型的错误发生，必须根据具体情况进行处理。
*/
//************************************
// Method:    	Port_Read_Block				从串口读数据（阻塞）
// Programmer:  lww
// Time:		2019/2/16
// Returns:   	int							正确：返回读取字节数，错误：返回-1
// Parameter: 	int fd						串口设备描述符
// Parameter: 	char * Data					存放接收数据的缓存区指针
// Parameter: 	int len						读取数据的长度
//************************************
int Port_Read_Block(int fd, void* Data, int len, unsigned int wait_ms)
{
	fd_set rfds;
	struct timeval time;
	int ret;
	int num;
	int _iLen = 0;

	MS2Timeval(&time, wait_ms);
	while (len != 0)
	{
		//< 初始化文件描述符集合
		FD_ZERO(&rfds);////< 将参数指向的文件描述符集合初始化，即清空所有的文件描述符 (如果不清空，由于在系统分配内存空间后，通常并不作清空处理，所以结果是不可知的)
		//< 设置串口文件描述符到文件描述符集合中
		FD_SET(fd, &rfds);   ////< 设置参数一文件描述符到参数二指向的集合中
		//< 对文件描述符进行监视
		do {
			if (wait_ms == 0)
			{   
				//select:测试指定的文件描述集合sock中可读？可写？有异常条件待处理？
				ret = select(fd + 1, &rfds, NULL, NULL, NULL);		//< duan0528 死等
			}
			else
			{
				ret = select(fd + 1, &rfds, NULL, NULL, &time);
			}
		} while (ret < 0 && errno == EINTR);//< 防止信号被以外打断而达不到阻塞或定时的效果

		if (ret < 0) { perror("Port read wrong\r\n"); break; }
		else if (ret == 0) { /*printf("Port read time out in Port%d\r\n", fd);*/ break; }
		else
		{
			if (FD_ISSET(fd, &rfds) != 0) //用于测试指定的文件描述符是否在该集合中 (fd 是否在 rfds 中)
			{
				num = (int)read(fd, Data, len);
				if (num == 0) { break; }
				if (num < 0)
				{
					if (errno == EINTR)
						continue;
					perror("Port read wrong!\n");
					_iLen = -1;
					break;
				}
				len -= num;
			    Data = (char*)Data + num;		
				_iLen += num;
			}
		}
	}
	return _iLen;
}
//************************************
// Method:    	Port_Read_UnBlock			从串口读数据（非阻塞状态，该状态下不存在超时的概念）
// Programmer:  lww
// Time:		2019/2/16
// Returns:   	int							正确：返回读取字节数，错误：返回-1
// Parameter: 	int fd						串口设备描述符
// Parameter: 	char * Data					存放接收数据的缓存区指针
// Parameter: 	int len						读取数据的长度
//************************************
int Port_Read_UnBlock(int fd, void* Data, int len)
{
	int _iLen = 0;

	while (1)
	{
		_iLen = (int)read(fd, Data, len);
		if (_iLen == -1)
		{
			if (errno == EINTR)
				continue;
			if (errno == EAGAIN)
			{
				perror("Port don't have data to read\n");
				break;
			}
			else
			{
				perror("Port read wrong!\n");
				break;
			}
		}
		else
			break;
	}
	return _iLen;
}


//************************************
// Method:    	Port_Send					串口发送函数
// Programmer:  lww
// Time:		2019/2/16
// Returns:   	int							串口发送数据字节数
// Parameter: 	int fd						串口设备描述符
// Parameter: 	char * Data					存放串口发送的数据的内存区域的指针
// Parameter: 	int len						发送的数据长度
// Parameter: 	unsigned int wait_ms		超时设置，如设置为0，则一直阻塞等待
// Parameter: 	bool Is_Block				是否以阻塞方式发送数据，true：是，false：否
//************************************
int Port_Send(int fd, void* Data, int len, unsigned int wait_ms, bool Is_Block)
{
	int _iLen = 0;

	if (Is_Block)
		_iLen = Port_Write_Block(fd, Data, len, wait_ms);
	else
		_iLen = Port_Write_UnBlock(fd, Data, len);

//	Clear_Port_Register(fd, CLEAR_OUT);

	return _iLen;
}


/*写数据到串口*/
/*
	ssize_t write(int fd, const void *buf, size_t count)
	write系统调用中可能遇到的情况及其处理方法：
		1.	调用返回值等于count，说明数据全部写入成功；
		2.	调用返回一个大于0小于count的值，说明部分数据没有写入。这可能是因为写入过程被信号打断，或者底层的设备暂时没有足够的空间存放所写入的数据。
		3.	调用阻塞，说明暂时不能写入数据，这种情况下如果以非阻塞方式操作文件，那么会立即返回错误。
		4.	调用返回-1，并且errno变量被设置为EINTR，表示在写入一个有效字节前收到一个信号，应用程序可以再次进行写操作。
		5.	调用返回-1，并且errno变量被设置为EAGAIN，这说明是在非阻塞方式下写文件，但文件暂时不能写入数据。
		6.	调用返回-1，并且errno变量被设置为EBADF，表示给定的文件描述符非法，或者文件不是以写方式打开的。
		7.	调用返回-1，并且errno变量被设置为EFAULT，表示buf是无效指针。
		8.	调用返回-1，并且errno变量被设置为EFBIG，表示写入的数据超过了最大的文件尺寸，或者超过了允许的文件的读写位置。
		9.	调用返回-1，并且errno变量被设置为EPIPE，说明写入是发生了数据通道断裂的错误，这种情况只在文件时管道或socket的情况下发生。这种情况下，进程还将收到
			一个SIGPIPE信号，信号的默认处理程序是使进程退出。
		10.	调用返回-1，并且errno变量被设置为ENOSPC，说明底层设备没有足够的空间。
*/
//************************************
// Method:    	Port_Write_Block				写数据到串口（阻塞）
// Programmer:  lww
// Time:		2019/2/16
// Returns:   	int								正确：返回发送字节数，错误：返回-1
// Parameter: 	int fd							串口设备描述符
// Parameter: 	char * Data						存放发送数据的缓存区指针
// Parameter: 	int len							发送数据的长度
//************************************
int Port_Write_Block(int fd, void* Data, int len, unsigned int wait_ms)
{
	fd_set rfds;
	struct timeval time;
	int num;
	int ret;
	int _iLen = 0;

	MS2Timeval(&time, wait_ms);
	while (len != 0)
	{
		//< 初始化文件描述符集合
		FD_ZERO(&rfds);
		//< 设置串口文件描述符到文件描述符集合中
		FD_SET(fd, &rfds);
		//< 对文件描述符进行监视
		do {
			if (wait_ms == 0)
			{
				ret = select(fd + 1, NULL, &rfds, NULL, NULL);
			}
			else
			{
				ret = select(fd + 1, NULL, &rfds, NULL, &time);
			}
		} while (ret < 0 && errno == EINTR);

		if (ret < 0) { perror("Port write wrong\r\n"); break; }
		else if (ret == 0) { printf("Port write time out in Port%d\r\n", fd); break; }
		else
		{
			if (FD_ISSET(fd, &rfds) != 0)
			{
				num = (int)write(fd, Data, len);
				if (num < 0)
				{
					if (errno == EINTR)
						continue;
					perror("Port write wrong!\n");
					_iLen = -1;
					break;
				}
				len -= num;
				Data = (char*)Data + num;
				_iLen += num;
			}
		}
	}
	return _iLen;
}
//************************************
// Method:    	Port_Write_UnBlock				写数据到串口（非阻塞）
// Programmer:  lww
// Time:		2019/2/16
// Returns:   	int								正确：返回发送字节数，错误：返回-1
// Parameter: 	int fd							串口设备描述符
// Parameter: 	char * Data						存放发送数据的缓存区指针
// Parameter: 	int len							发送数据的长度
//************************************
int Port_Write_UnBlock(int fd, void* Data, int len)
{
	int _iLen = 0;

	while (1)
	{
		_iLen = (int)write(fd, Data, len);
		if (_iLen == -1)
		{
			if (errno == EINTR)
				continue;
			if (errno == EAGAIN)
			{
				perror("Port can't write this moment\n");
				break;
			}
			else
			{
				perror("Port wtire wrong!\n");
				break;
			}
		}
		else
			break;
	}
	return _iLen;
}


//************************************
// Method:    	Printf_Repoint			printf重定向，由控制台重定向至串口
// Programmer:  lww
// Time:		2019/1/16
// Returns:   	bool
//************************************
bool Printf_Repoint()
{
#define DUP2_DEAL(var)	{\
	if((var) < 0)	{\
		printf("dup2 wrong\r\n");\
		return false;\
	}\
}
	int fd = -1;

	//< 参数一：打开串口的设备描述符
	//< 参数二：属性设置，依次为：
	//< 		O_RDWR——读写打开
	//<			O_NOCTTY——如果设备描述符指的是终端设备，则不将此设备分配作为此进程的控制终端(可以告诉Linux这个程序不会成为这个端口上的“控制终端”.如果不这样做的话,所有的输入,比如键盘上过来的Ctrl+C中止信号等等,会影响到你的进程。)
	//<			O_NDELAY——告诉Linux,这个程序并不关心DCD信号线的状态——也就是不关心端口另一端是否已经连接(非阻塞)
	if ((fd = Port_Open(PORT_DEBUG, false, O_RDWR | O_NOCTTY | O_NDELAY, B115200, CS8, 0, 1)) < 0)
		return false;

	DUP2_DEAL(dup2(fd, 0));	//< 标准输入重定向
	DUP2_DEAL(dup2(fd, 1));	//< 标准输出重定向
	DUP2_DEAL(dup2(fd, 2));	//< 标准错误重定向

	RoughDelay_S(2);

#undef DUP2_DEAL

	return true;
}


//************************************
// Method:    	BandRate_Trans					波特率转化为Linux系统用格式
// Programmer:  lww
// Time:		2019/2/24
// Returns:   	unsigned int					转化后的波特率数值
// Parameter: 	int value						波特率数值
//************************************
unsigned int BandRate_Trans(int value)
{
#define CASE(name)	case name:	return B##name;	break;
	switch (value) {
		CASE(0);
		CASE(50);
		CASE(75);
		CASE(110);
		CASE(134);
		CASE(150);
		CASE(200);
		CASE(300);
		CASE(600);
		CASE(1200);
		CASE(1800);
		CASE(2400);
		CASE(4800);
		CASE(9600);
		CASE(19200);
		CASE(38400);
		CASE(57600);
		CASE(115200);
		CASE(230400);
		CASE(460800);
		CASE(500000);
		CASE(576000);
		CASE(921600);
		CASE(1000000);
		CASE(1152000);
		CASE(1500000);
		CASE(2000000);
		CASE(2500000);
		CASE(3000000);
		CASE(3500000);
		CASE(4000000);
	default:		return B9600;		break;
	}
#undef CASE
}


//************************************
// Method:    	DataBits_Trans					数据位转化为Linux系统用格式
// Programmer:  lww
// Time:		2019/2/24
// Returns:   	unsigned int					转化后的数据位数值
// Parameter: 	char value						数据位数值
//************************************
unsigned int DataBits_Trans(int value)
{
#define CASE(name)	case name:	return CS##name;	break;
	switch (value) {
		CASE(5);
		CASE(6);
		CASE(7);
		CASE(8);
	default:		return CS8;			break;
	}
#undef CASE
}


//************************************
// Method:    	Clear_Port_Register				清空串口缓存区
// Programmer:  lww
// Time:		2019/2/28
// Returns:   	bool							成功-true；失败-false
// Parameter: 	int fd							串口设备描述符
// Parameter: 	Port_Clear_Mode_e mode			串口清空模式
//************************************
bool Clear_Port_Register(int fd, Port_Clear_Mode_e mode)
{
	int ret;
	do {
		switch (mode) {
		case CLEAR_IN:	ret = tcflush(fd, TCIFLUSH);	break;
		case CLEAR_OUT:	ret = tcflush(fd, TCOFLUSH);	break;
		case CLEAR_IN_AND_OUT:	ret = tcflush(fd, TCIOFLUSH);	break;
		}
	} while (ret < 0 && errno == EINTR);
	ExactDelay(10);
	if (ret < 0)
	{
		perror("Port recv or send register clear failed\n");
		return false;
	}
	else
		return true;
}
